package com.quarkdown.core.util

import com.quarkdown.core.ast.AstRoot
import com.quarkdown.core.ast.InlineContent
import com.quarkdown.core.ast.NestableNode
import com.quarkdown.core.ast.Node
import com.quarkdown.core.ast.base.inline.CriticalContent
import com.quarkdown.core.ast.base.inline.PlainTextNode
import com.quarkdown.core.ast.dsl.buildInline
import com.quarkdown.core.ast.quarkdown.inline.TextSymbol
import com.quarkdown.core.visitor.node.NodeVisitor

/**
 * Returns a sequence of all nodes in the tree, where [this] is the root node.
 * The sequence is generated by traversing the tree in depth-first order.
 * The root node is excluded from the sequence.
 *
 * Example:
 *
 * Input (nested node tree):
 * ```
 * AstRoot
 *   BlockQuote
 *     Paragraph
 *       Text
 *   Paragraph
 *     Strong
 *       Text
 *     Text
 *     Emphasis
 *       Text
 * ```
 *
 * Output (flattened sequence):
 * ```
 * BlockQuote
 * Paragraph
 * Text
 * Paragraph
 * Strong
 * Text
 * Text
 * Emphasis
 * Text
 * ```
 *
 * @return flattened sequence of children nodes, excluding [this] root node
 */
fun NestableNode.flattenedChildren(): Sequence<Node> =
    sequence {
        // DFS traversal.
        for (child in children) {
            yield(child)
            if (child is NestableNode) {
                yieldAll(child.flattenedChildren())
            }
        }
    }

/**
 * Converts processed [InlineContent] to its plain text representation.
 * For example, the Markdown input `foo **bar `baz`**` has `foo bar baz` as its plain text.
 * @param renderer optional renderer to use to render critical content and text symbols
 * @return plain text of the inline content
 * @see PlainTextNode
 */
fun InlineContent.toPlainText(renderer: NodeVisitor<CharSequence>? = null): String {
    val builder = StringBuilder()

    // Visits the tree and appends the text content of each node.
    AstRoot(this).flattenedChildren().forEach {
        when {
            it is CriticalContent && renderer != null -> builder.append(renderer.visit(it))
            it is TextSymbol && renderer != null -> builder.append(renderer.visit(it))
            it is PlainTextNode -> builder.append(it.text)
        }
    }

    return builder.toString()
}

/**
 * Strips rich content from [this] inline content and returns a new inline content with only one [com.quarkdown.core.ast.base.inline.Text] child,
 * which contains the plain text representation of [this] inline content.
 * @param renderer optional renderer to use to render critical content and text symbols
 * @return inline content with only plain text
 * @see toPlainText
 */
fun InlineContent.stripRichContent(renderer: NodeVisitor<CharSequence>? = null): InlineContent = buildInline { text(toPlainText(renderer)) }
